/*
 * Copyright (C) 2020 The LINN Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.juqimiao.linn.command;

import com.juqimiao.linn.context.ApplicationContextWrapperI;
import com.juqimiao.linn.exception.FrameworkException;
import com.juqimiao.linn.interception.InterceptorKeeperI;
import com.juqimiao.linn.interception.InterceptorRegistry;
import com.juqimiao.linn.logging.Logger;
import com.juqimiao.linn.registry.RegistryI;
import com.juqimiao.linn.transfer.Command;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 命令信息注册类。它会自动收集所有实现了{@link CommandExecutorI}和被标注了{@link CommandExecutor}的执行器.
 *
 * 可选的，如果在应用程序配置文件中配置了
 * leda:
 *  interception:
 *   interceptor:
 *      - com.juqimiao.linn.interception.CommandInterceptorI
 *
 * 请查看 {@link InterceptorRegistry} 了解拦截器配置情况。
 *
 * @author  Collin Cai
 */
@Component("command")
@Order
public class CommandRegistry implements RegistryI {

    // ---- Static Fields ----------/

    private static final String ERROR_TEMPLATE = "The executing method in %s "
            + "should has one parameter which implement from BaseCommand。";

    private static final String ERROR_IMPLEMENTATION_TEMPLATE = "The %s should be implemented from %s。";

    private static final String EXECUTE_METHOD_NAME = "execute";


    // ---- Field ----------/

    private ApplicationContextWrapperI applicationContextWrapper;

    private CommandHub commandHub;

    private Logger logger = Logger.getLogger(CommandRegistry.class);
    // ---- Constructor ----------/

    /**
     *  构造器。
     * @param applicationContextWrapper 一个 {@link org.springframework.context.ApplicationContext} 的包装对象。
     * @param commandHub 一个命令中心对象。
     */
    @Autowired
    public CommandRegistry(@Autowired ApplicationContextWrapperI applicationContextWrapper,
                           @Autowired CommandHub commandHub) {
        this.applicationContextWrapper = applicationContextWrapper;
        this.commandHub = commandHub;
    }

    // ---- Methods ----------/

    /**
     * 执行Command注册过程。
     */
    @Override
    public void register() {
        /* 1. 通过CommandExecutorI接口获取所有自定义CommandExecutor*/
        logger.info("Begin Command register process.");

        Map<String, CommandExecutorI> executors = applicationContextWrapper.getBeansOfType(CommandExecutorI.class);
        /* 2. 遍历集合中所有 CommandExecutor */
        executors.forEach((key, executor) -> {
            logger.info(String.format("Begin register %s .", executor.getClass().getName()));
            Class<?> executorClz = executor.getClass();
            /* 3. 获取execute方法信息, 并检查方法和参数是否符合execute方法定义 */
            Method method = checkAndGetMethod(executorClz);
            Class parameterType = checkAndGetParameterType(method);
            /* 4. 以Command类型为 Key，保存到 CommandHub 中*/
            CommandInvocation commandInvocation = applicationContextWrapper.getBean(CommandInvocation.class);
            commandInvocation.setCommandExecutor(executor);
            /* 如果 interceptorKeeper被指定，将收集 Interceptors*/

            commandHub.getCommandKeeper().put(parameterType, commandInvocation);
            /* Todo: 考虑是否需要Response Repository */
            commandHub.getResponseKeeper().put(parameterType, method.getReturnType());
            logger.info(String.format("End register %s .", executor.getClass().getName()));
        });

        logger.info("End Command register process.");
    }

    /**
     * 返回execute方法信息, 并检查方法是否符合定义。
     *
     * @param executorClz {@link CommandExecutorI}的{@link Class<?>}反射类。
     * @return 代表execute方法的反射类型。
     */
    private Method checkAndGetMethod(Class<?> executorClz) {
        Method method = null;
        Method[] methods = executorClz.getDeclaredMethods();

        try {
            if (methods.length == 0) {
                String msg = String.format(ERROR_IMPLEMENTATION_TEMPLATE,
                        executorClz.getName(),
                        CommandExecutorI.class.getName());
                throw new FrameworkException(msg);
            }

            Optional<Method> optional = Arrays.stream(methods)
                    .filter(item -> !item.isBridge()
                            && CommandRegistry.EXECUTE_METHOD_NAME.equals(item.getName()))
                    .findFirst();
            if (optional.isPresent()) {
                method = optional.get();
            }
        } catch (Exception e) {
            String msg = String.format(ERROR_IMPLEMENTATION_TEMPLATE,
                    executorClz.getName(),
                    CommandExecutorI.class.getName());
            throw new FrameworkException(msg, e);
        }

        if (method == null) {
            String msg = String.format(ERROR_IMPLEMENTATION_TEMPLATE,
                    executorClz.getName(),
                    CommandExecutorI.class.getName());
            throw new FrameworkException(msg);
        }

        return method;
    }

    /**
     * 查看是否符合CommandExecutorI中定义的 execute方法的声明。
     * 检查：参数是否只有一个。
     * 检查：参数是否继承于 { @link BaseCommand } 类。
     *
     * @param method 代表execute方法的反射类型。
     * @return 代表execute方法中继承 {@link Command}类的参数的反射类型。
     */
    private Class<?> checkAndGetParameterType(Method method) {
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (!(parameterTypes.length == 1
                && Command.class.isAssignableFrom(parameterTypes[0]))) {
            String msg = String.format(ERROR_TEMPLATE, method.getDeclaringClass());
            throw new FrameworkException(msg);
        }
        return parameterTypes[0];
    }
}
